package sk.jp.commons.utils.xml;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

/**
 * Utility class for XSLT operations.
 *
 * @author Jan Pastor
 */
public class XsltUtils {

    private XsltUtils(){
    }

    //----------------------------------------------------------------------

    /**
     * Transforms input XML with XSLT to string.
     * @param xml XML to transform.
     * @param xslt XSL template.
     * @return transformation result in string.
     * @throws Exception
     */
    public static String transform(String xml, String xslt) throws Exception {
        Source source = new StreamSource( new StringReader(xml) );
        StringWriter resultWriter = new StringWriter();
        Result result = new StreamResult( resultWriter );
        Source xsltSource = new StreamSource( new StringReader(xslt) );
        transform(source, result, xsltSource);
        return resultWriter.toString();
    }

    /**
     * Transforms input DOM node with XSLT to DOM.
     * @param node root node of document fragment to transform.
     * @param xslt XSL template.
     * @return transformation result Document in DOM.
     * @throws Exception
     */
    public static Document transform(Node node, String xslt) throws Exception {
        Source source = new DOMSource(node);
        Document resultDoc = XmlBuilderTools.getInstance().createDocument();
        Result result = new DOMResult(resultDoc);
        Source xsltSource = new StreamSource( new StringReader(xslt) );
        transform(source, result, xsltSource);
        return resultDoc;
    }

    /**
     * Transforms input DOM node with XSLT to string.
     * @param node root node of document fragment to transform.
     * @param xslt XSL template.
     * @return transformation result in string.
     * @throws Exception
     */
    public static String transformToString(Node node, String xslt) throws Exception {
        Source source = new DOMSource(node);
        StringWriter resultWriter = new StringWriter();
        Result result = new StreamResult( resultWriter );
        Source xsltSource = new StreamSource( new StringReader(xslt) );
        transform(source, result, xsltSource);
        return resultWriter.toString();
    }

    /**
     * Performs XSLT transformation.
     * @param source input source.
     * @param result result.
     * @param xslt XSL template.
     * @throws Exception if any exception occurs.
     */
    public static void transform(Source source, Result result, Source xslt)
                            throws Exception {
        try {
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer(xslt);
            transformer.transform(source, result);
        }catch(Exception e){
            throw new Exception("Transformation failed", e);
        }
    }

    //----------------------------------------------------------------------

    /**
     * Implementation of <code>ExecutableUtility</code> interface.
     * @param args utility arguments. [0] : input XML file path, [1] : output file
     * path (created), [2] : XSLT file path, (optional) [3] : charset used for
     * read XML and XSLT files (default is the <code>system default</code>).
     * @throws Exception
     */
    public void execute(Object[] args) throws Exception {
        System.out.println("XSLT Transformation Utility\n");
        try {
            if(args.length < 3){
                throw new IllegalArgumentException("Missing required arguments");
            }
            String inpFileName = args[0].toString();
            String outFileName = args[1].toString();
            String xsltFileName = args[2].toString();
            String charsetName = null; //= "UTF-8";
            if(args.length > 3){
                charsetName = args[3].toString();
            }
            //Load files:
            String inpStr;
            String xsltStr;
            //Load inp file content:
            File inpFile = new File(inpFileName);
            if(!inpFile.canRead()){
                throw new IOException("Can't read file '"+inpFileName+"'");
            }
            FileInputStream fis = new FileInputStream(inpFile);
            byte[] data = new byte[ (int)inpFile.length() ];
            if( inpFile.length() != fis.read(data) ){
                throw new IOException("Read file content failed");
            }
            if(charsetName != null){
                inpStr = new String(data, charsetName);
            }else{
                inpStr = new String(data);
            }
            //Load inp file content:
            File xsltFile = new File(xsltFileName);
            if(!xsltFile.canRead()){
                throw new IOException("Can't read file '"+xsltFileName+"'");
            }
            fis = new FileInputStream(xsltFile);
            data = new byte[ (int)xsltFile.length() ];
            if( xsltFile.length() != fis.read(data) ){
                throw new IOException("Read file content failed");
            }
            if(charsetName != null){
                xsltStr = new String(data, charsetName);
            }else{
                xsltStr = new String(data);
            }
            //transform:
            String outStr = transform(inpStr, xsltStr);
            //Save output:
            File outFile = new File(outFileName);
            if(!outFile.exists()){
                outFile.createNewFile();
            }
            if(!outFile.canWrite()){
                throw new IOException("Can't write to file '"+outFileName+"'");
            }
            FileOutputStream fos = new FileOutputStream(outFile);
            if(charsetName != null){
                data = outStr.getBytes(charsetName);
            }else{
                data = outStr.getBytes();
            }
            fos.write(data);
        }catch(Exception e){
            throw new Exception("Transformation failed (args: "+
                                Arrays.asList(args).toString()+")", e);
        }
    }

}
